/*
 * Copyright 2019. Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.android.apps.santatracker.doodles.shared.sound;

import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.AsyncTask;
import com.google.android.apps.santatracker.AudioConstants;
import com.google.android.apps.santatracker.data.SantaPreferences;
import com.google.android.apps.santatracker.util.SantaLog;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/** A manager for all of the different sounds which will be played in the games. */
public class SoundManager {
    private static final String TAG = SoundManager.class.getSimpleName();
    public static boolean soundsAreMuted;
    private static SoundManager instance;
    private final Object mediaPlayerLock = new Object();
    private final Object soundPoolLock = new Object();

    // A map from resource ID to a media player which can play the sound clip.
    private Map<Integer, DoodleMediaPlayer> mediaPlayerMap;
    // A map from resource ID to a sound pool containing the sound clip.
    private Map<Integer, SoundPoolContainer> soundPoolMap;
    // A set of resource IDs for sounds which have been individually muted. These shouldn't be
    // unmuted
    // when the mute option is toggled.
    private Set<Integer> mutedSounds;

    private SantaPreferences mSantaPreferences;

    private SoundManager() {
        mediaPlayerMap = new HashMap<>();
        soundPoolMap = new HashMap<>();
        mutedSounds = new HashSet<>();
    }

    public static SoundManager getInstance() {
        if (instance == null) {
            instance = new SoundManager();
        }
        return instance;
    }

    public void loadLongSound(Context context, int resId, boolean looping, float volume) {
        if (mediaPlayerMap.containsKey(resId)) {
            return;
        }
        DoodleMediaPlayer mediaPlayer;
        if (looping) {
            mediaPlayer = LoopingMediaPlayer.create(context, resId);
        } else {
            mediaPlayer = DoodleMediaPlayerImpl.create(context, resId);
        }
        mediaPlayer.setVolume(volume);
        if (soundsAreMuted) {
            mediaPlayer.mute();
        }
        synchronized (mediaPlayerLock) {
            mediaPlayerMap.put(resId, mediaPlayer);
        }
    }

    public void loadLongSound(Context context, int resId, boolean looping) {
        // Make this quiet by default so that it doesn't overpower the in-game sounds.
        loadLongSound(context, resId, looping, AudioConstants.DEFAULT_BACKGROUND_VOLUME);
    }

    public void loadShortSound(
            final Context context, final int resId, final boolean looping, final float volume) {
        if (soundPoolMap.containsKey(resId)) {
            return;
        }
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                SoundPoolContainer container =
                        new SoundPoolContainer(context, resId, looping, volume);
                synchronized (soundPoolLock) {
                    soundPoolMap.put(resId, container);
                }
                return null;
            }
        }.execute();
    }

    public void loadShortSound(Context context, int resId) {
        loadShortSound(context, resId, false, AudioConstants.DEFAULT_SOUND_EFFECT_VOLUME);
    }

    public void play(int resId) {
        if (mediaPlayerMap.containsKey(resId)) {
            mediaPlayerMap.get(resId).start();
        } else if (soundPoolMap.containsKey(resId)) {
            SoundPoolContainer container = soundPoolMap.get(resId);
            float vol = soundsAreMuted ? 0 : container.volume;
            container.streamId =
                    container.soundPool.play(
                            container.soundId, vol, vol, 1, container.looping ? -1 : 0, 1);
        }
    }

    public void pauseAll() {
        synchronized (mediaPlayerLock) {
            for (int resId : mediaPlayerMap.keySet()) {
                pause(resId);
            }
        }
        synchronized (soundPoolLock) {
            for (int resId : soundPoolMap.keySet()) {
                pause(resId);
            }
        }
    }

    public void pause(int resId) {
        if (mediaPlayerMap.containsKey(resId)) {
            DoodleMediaPlayer mediaPlayer = mediaPlayerMap.get(resId);
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.pause();
            }
        } else if (soundPoolMap.containsKey(resId)) {
            soundPoolMap.get(resId).soundPool.autoPause();
        }
    }

    public void pauseShortSounds() {
        synchronized (soundPoolLock) {
            for (SoundPoolContainer container : soundPoolMap.values()) {
                container.soundPool.autoPause();
            }
        }
    }

    public void resumeShortSounds() {
        synchronized (soundPoolLock) {
            for (SoundPoolContainer container : soundPoolMap.values()) {
                container.soundPool.autoResume();
            }
        }
    }

    public void playLongSoundsInSequence(int[] soundIds) {
        for (int i = 0; i < soundIds.length; i++) {
            if (isPlayingLongSound(soundIds[i])) {
                // Don't try to play long sounds which are already playing.
                return;
            }
        }

        try {
            DoodleMediaPlayer[] sounds = new DoodleMediaPlayer[soundIds.length];
            for (int i = 0; i < soundIds.length; i++) {
                sounds[i] = mediaPlayerMap.get(soundIds[i]);
                if (sounds[i] == null) {
                    return;
                }
                if (i > 0) {
                    sounds[i - 1].setNextMediaPlayer(sounds[i].getMediaPlayer());
                }
            }
            sounds[0].start();
        } catch (IllegalStateException e) {
            SantaLog.d(TAG, "playLongSoundsInSequence() failed: " + e.toString());
        }
    }

    public boolean isPlayingLongSound(int resId) {
        if (mediaPlayerMap.containsKey(resId)) {
            return mediaPlayerMap.get(resId).isPlaying();
        }
        return false;
    }

    public void releaseAll() {
        synchronized (mediaPlayerLock) {
            for (DoodleMediaPlayer mediaPlayer : mediaPlayerMap.values()) {
                mediaPlayer.release();
            }
            mediaPlayerMap.clear();
        }
        synchronized (soundPoolLock) {
            for (SoundPoolContainer container : soundPoolMap.values()) {
                container.soundPool.release();
            }
            soundPoolMap.clear();
        }
    }

    public void release(int resId) {
        if (mediaPlayerMap.containsKey(resId)) {
            mediaPlayerMap.get(resId).release();
            mediaPlayerMap.remove(resId);
        }
        if (soundPoolMap.containsKey(resId)) {
            soundPoolMap.get(resId).soundPool.release();
            soundPoolMap.remove(resId);
        }
    }

    public void mute() {
        synchronized (mediaPlayerLock) {
            for (int resId : mediaPlayerMap.keySet()) {
                muteInternal(resId, false);
            }
        }
        synchronized (soundPoolLock) {
            for (int resId : soundPoolMap.keySet()) {
                muteInternal(resId, false);
            }
        }
        soundsAreMuted = true;
    }

    public void mute(int resId) {
        muteInternal(resId, true);
    }

    private void muteInternal(int resId, boolean addToMutedSounds) {
        if (mediaPlayerMap.containsKey(resId)) {
            mediaPlayerMap.get(resId).mute();
        }
        if (soundPoolMap.containsKey(resId)) {
            SoundPoolContainer container = soundPoolMap.get(resId);
            container.soundPool.setVolume(container.streamId, 0, 0);
        }
        if (addToMutedSounds) {
            mutedSounds.add(resId);
        }
    }

    public void unmute() {
        soundsAreMuted = false;
        synchronized (mediaPlayerLock) {
            for (int resId : mediaPlayerMap.keySet()) {
                if (!mutedSounds.contains(resId)) {
                    unmuteInternal(resId, false);
                }
            }
        }
        synchronized (soundPoolLock) {
            for (int resId : soundPoolMap.keySet()) {
                if (!mutedSounds.contains(resId)) {
                    unmuteInternal(resId, false);
                }
            }
        }
    }

    public void unmute(int resId) {
        unmuteInternal(resId, true);
    }

    private void unmuteInternal(int resId, boolean removeFromMutedSounds) {
        if (soundsAreMuted) {
            return;
        }
        if (mediaPlayerMap.containsKey(resId)) {
            mediaPlayerMap.get(resId).unmute();
        }
        if (soundPoolMap.containsKey(resId)) {
            SoundPoolContainer container = soundPoolMap.get(resId);
            container.soundPool.setVolume(container.streamId, container.volume, container.volume);
        }
        if (removeFromMutedSounds) {
            mutedSounds.remove(resId);
        }
    }

    public void storeMutePreference(final Context context) {
        if (mSantaPreferences == null) {
            mSantaPreferences = new SantaPreferences(context.getApplicationContext());
        }
        mSantaPreferences.setMuted(soundsAreMuted);
    }

    public void loadMutePreference(final Context context) {
        if (mSantaPreferences == null) {
            mSantaPreferences = new SantaPreferences(context.getApplicationContext());
        }
        if (mSantaPreferences.isMuted()) {
            mute();
        } else {
            unmute();
        }
    }

    private static class SoundPoolContainer {
        public final SoundPool soundPool;
        public final int soundId;
        public final boolean looping;
        public final float volume;
        public int streamId;

        public SoundPoolContainer(Context context, int resId, boolean looping, float volume) {
            soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
            soundId = soundPool.load(context, resId, 1);
            this.looping = looping;
            this.volume = volume;
        }
    }
}
